from _decimal import Decimal

from django.utils import timezone
from django_redis import get_redis_connection
from rest_framework import serializers

from goods.models import SKU
from .models import OrderInfo, OrderGoods
from django.db import transaction


class CommitOrderSerializer(serializers.ModelSerializer):
    """提交订单"""

    class Meta:
        model = OrderInfo
        # order_id ：输出；address 和 pay_method : 输入.k
        fields = ('order_id', 'address', 'pay_method')
        read_only_fields = ('order_id',)
        # 指定address 和 pay_method 为输出
        extra_kwargs = {
            'address': {
                'write_only': True,
            },
            'pay_method': {
                'write_only': True,
            }
        }

    def create(self, validated_data):
        """重写create方法实现订单表、订单商品表、sku、spu信息的存储
        """
        # 获取当前保存订单时需要的信息
        user = self.context['request'].user
        # order_id == '时间+userid' == '20180914122126000000001'
        order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)
        address = validated_data.get('address')
        pay_method = validated_data.get('pay_method')

        # 从这里"明显"的开启一次事务
        with transaction.atomic():

            # 在数据操作的之前，就创建一个保存点，将来用于回滚和提交到此
            save_id = transaction.savepoint()

            try:
                # 保存订单基本信息 OrderInfo（一）
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,
                    total_amount=Decimal('0.00'),
                    freight=Decimal('10.00'),
                    pay_method=pay_method,
                    # status = '待支付' if '支付方式是支付宝支付' else '货到付款'
                    status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM[
                        'ALIPAY'] else OrderInfo.ORDER_STATUS_ENUM['UNSEND']
                )

                # 从redis读取购物车中被勾选的商品信息
                redis_conn = get_redis_connection('cart')

                # 读取购物车数据
                # redis_cart = {b'sku_id1':b'count_1', b'sku_id2':b'count_2',b'sku_id3':b'count_3'}
                redis_cart = redis_conn.hgetall('cart_%s' % user.id)
                # 读取被勾选的商品的sku_ids
                # redis_selected = [b'sku_id2', b'sku_id3']
                redis_selected = redis_conn.smembers('selected_%s' % user.id)

                # 构造被勾选的商品字典
                # carts = {sku_id2:count_2, sku_id3:coun_3}
                carts = {}
                for sku_id in redis_selected:
                    carts[int(sku_id)] = int(redis_cart[sku_id])

                # 获取要购买的商品的sku_id
                # sku_ids = [sku_id2, sku_id3]
                sku_ids = carts.keys()

                # 遍历购物车中被勾选的商品信息
                for sku_id in sku_ids:
                    while True:
                        # 获取sku对象
                        sku = SKU.objects.get(id=sku_id)

                        # 读取原始库存和销量
                        origin_stock = sku.stock
                        origin_sales = sku.sales

                        # 判断库存 
                        sku_count = carts.get(sku.id)
                        # 如果要购买的商品数量大于库存就抛出异常
                        if sku_count > origin_stock:
                            # 出错就回滚到保存点
                            transaction.savepoint_rollback(save_id)
                            raise serializers.ValidationError('库存不足')

                        # 模拟网络延迟,为了放大多人同时下单的错误效果，没有实际意义
                        # import time
                        # time.sleep(5)

                        # 减少库存，增加销量 SKU 
                        # sku.stock -= sku_count # sku.stock = sku.stock - sku_count
                        # sku.sales += sku_count # sku.sales = sku.sales + sku_count
                        # sku.save()

                        # 使用乐观锁并发下单，修改库存和销量
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count

                        # 在跟新数据之前，使用原始库存查询该商品记录是否存在，如果存在就跟新新的库存；反之，会返回0
                        result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock,
                                                                                          sales=new_sales)
                        if result == 0:
                            # 如果直接抛出异常，表示用户购买商品只有一次机会，即使库存满足也会下单失败
                            # raise serializers.ValidationError('下单失败')
                            continue

                        # 修改SPU销量
                        sku.goods.sales += sku_count
                        sku.goods.save()

                        # 保存订单商品信息 OrderGoods（多）
                        OrderGoods.objects.create(
                            order=order,  # order_id=order.id
                            sku=sku,  # sku_id=sku.id
                            count=sku_count,
                            price=sku.price
                        )

                        # 累加计算总数量和总价
                        order.total_count += sku_count
                        order.total_amount += (sku.price * sku_count)

                        # 如果下单成功就break，打破while死循环，进入下一个商品的购买
                        break

                # 最后加入邮费和保存订单信息
                order.total_amount += order.freight
                order.save()
            except serializers.ValidationError:
                # 抛出异常以前，不需要在这里回滚，因为之前回滚了才抛出次异常
                raise
            except Exception:
                # 回滚到保存单
                transaction.savepoint_rollback(save_id)
                raise  # 只要有异常就抛出，不关心是什么异常

            # 执行成功就提交事务到保存点
            transaction.savepoint_commit(save_id)

        # 清除购物车中已结算的商品
        pl = redis_conn.pipeline()
        pl.hdel('cart_%s' % user.id, *redis_selected)
        pl.srem('selected_%s' % user.id, *redis_selected)
        pl.execute()

        # 响应数据
        return order


class CartSKUSerializer(serializers.ModelSerializer):
    """
    购物车商品数据序列化器
    """
    count = serializers.IntegerField(label='数量')

    class Meta:
        model = SKU
        fields = ('id', 'name', 'default_image_url', 'price', 'count')


class OrderSettlementSerializer(serializers.Serializer):
    """
    订单结算数据序列化器
    """
    # float 1.23 ==> 123 * 10 ^ -2  --> 1.299999999
    # Decimal  1.23    1    23
    # max_digits 一共多少位；decimal_places：小数点保留几位
    freight = serializers.DecimalField(label='运费', max_digits=10, decimal_places=2)
    skus = CartSKUSerializer(many=True)